-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Streams] Add name and confirm step to create classic stream flyout #244998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[Streams] Add name and confirm step to create classic stream flyout #244998
Conversation
…tead readonly inputs
|
Pinging @elastic/kibana-management (Team:Kibana Management) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements the name and confirm step for the create classic stream flyout, allowing users to specify a stream name by filling in wildcards from selected index patterns.
Key changes:
- Adds stream name validation (empty wildcards, duplicates, higher priority conflicts)
- Implements dynamic input generation based on index pattern wildcards
- Moves template selection state management into the flyout component
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
utils/utils.ts |
New utility functions for data retention formatting, wildcard validation, and stream name validation |
stream_name_input/stream_name_input.tsx |
Component that dynamically generates inputs for index pattern wildcards with validation state |
stream_name_input/stream_name_input.test.tsx |
Comprehensive tests for the StreamNameInput component |
stream_name_input/stream_name_input.stories.tsx |
Storybook stories demonstrating various input patterns and validation states |
name_and_confirm/name_stream_section.tsx |
Section component for naming streams with index pattern selection and validation messages |
name_and_confirm/name_and_confirm_step.tsx |
Step component that orchestrates the name and confirm UI |
create_classic_stream_flyout.tsx |
Updated flyout to manage template selection state, validation, and stream creation flow |
create_classic_stream_flyout.test.tsx |
Updated tests covering navigation, validation, and callback behavior |
create_classic_stream_flyout.stories.tsx |
Updated stories with validation scenarios and mock data |
select_template_step.tsx |
Updated to use extracted formatDataRetention utility |
| export const validateStreamName = async ( | ||
| streamName: string, | ||
| onValidate?: StreamNameValidator | ||
| ): Promise<StreamNameValidationResult> => { |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function lacks a JSDoc comment explaining its purpose, parameters, and return value. Add documentation describing the validation flow and how empty wildcards are checked before the external validator runs.
| const buildStreamName = (pattern: string, parts: string[]): string => { | ||
| let partIndex = 0; | ||
| return pattern.replace(/\*/g, () => { | ||
| // Keep * if the part is empty, so validation can detect unfilled wildcards | ||
| const part = parts[partIndex] || '*'; | ||
| partIndex++; | ||
| return part; | ||
| }); | ||
| }; |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc documentation explaining the purpose of this function, its parameters (pattern and parts), and the return value. The comment on line 108 is helpful but insufficient for understanding the function's role in the component.
| isFirst: boolean; | ||
| isLast: boolean; | ||
| } | ||
|
|
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc documentation explaining that this function parses an index pattern string into segments of static text and wildcards, and describe the returned PatternSegment array structure.
| /** | |
| * Parses an index pattern string into an array of segments, where each segment is either | |
| * a static text segment or a wildcard segment. | |
| * | |
| * A static segment represents a sequence of characters between wildcards. | |
| * A wildcard segment represents a '*' character in the pattern. | |
| * | |
| * @param pattern - The index pattern string to parse (e.g., 'foo-*-bar-*'). | |
| * @returns {PatternSegment[]} An array of PatternSegment objects, where each object has: | |
| * - type: 'static' | 'wildcard' | |
| * - value: the string value of the segment (static text or '*') | |
| * - index: the zero-based index of the wildcard (only present for wildcards) | |
| */ |
|
|
||
| return segments; | ||
| }; | ||
|
|
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc documentation explaining how this function groups segments into input groups for rendering, including how prepend/append text is assigned to each group.
| /** | |
| * Groups pattern segments into input groups for rendering. | |
| * | |
| * Each input group corresponds to a wildcard segment in the pattern, and may have | |
| * static text prepended or appended to it: | |
| * - Static text immediately preceding a wildcard becomes the `prepend` for that group. | |
| * - Static text immediately following the last wildcard becomes the `append` for the last group. | |
| * - If there is no static text before a wildcard, `prepend` is undefined. | |
| * - If there is no static text after the last wildcard, `append` is undefined. | |
| * - The `isFirst` and `isLast` flags indicate whether the group is the first or last wildcard group. | |
| * | |
| * Example: | |
| * For pattern "foo-*-bar-*", segments would be: | |
| * [static "foo-", wildcard, static "bar-", wildcard] | |
| * This produces two groups: | |
| * 1. prepend: "foo-", wildcardIndex: 0, isFirst: true, isLast: false | |
| * 2. prepend: "bar-", wildcardIndex: 1, isFirst: false, isLast: true | |
| * | |
| * @param segments Array of pattern segments (static and wildcard) | |
| * @returns Array of input groups for rendering, with prepend/append text assigned | |
| */ |
|
|
||
| return groups; | ||
| }; | ||
|
|
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc documentation explaining that this function counts the number of wildcard characters (*) in the pattern string.
| /** | |
| * Counts the number of wildcard characters (*) in the given pattern string. | |
| * | |
| * @param pattern - The pattern string to search for wildcards. | |
| * @returns The number of wildcard (*) characters in the pattern. | |
| */ |
| const handleCreate = useCallback(async () => { | ||
| setHasAttemptedSubmit(true); | ||
| setIsValidating(true); | ||
|
|
||
| try { | ||
| const result = await validateStreamName(streamName, onValidate); | ||
| setValidationError(result.errorType); | ||
| setConflictingIndexPattern(result.conflictingIndexPattern); | ||
|
|
||
| if (result.errorType === null) { | ||
| onCreate(streamName); | ||
| } | ||
| } finally { | ||
| setIsValidating(false); | ||
| } | ||
| }, [streamName, onValidate, onCreate]); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc documentation explaining that this callback validates the stream name before creation, sets validation state, and only calls onCreate if validation passes.
| const runValidation = useCallback( | ||
| async (name: string): Promise<boolean> => { | ||
| setIsValidating(true); | ||
| try { | ||
| const result = await validateStreamName(name, onValidate); | ||
| setValidationError(result.errorType); | ||
| setConflictingIndexPattern(result.conflictingIndexPattern); | ||
| return result.errorType === null; | ||
| } finally { | ||
| setIsValidating(false); | ||
| } | ||
| }, | ||
| [onValidate] | ||
| ); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc documentation explaining that this function runs validation, updates state with the results, and returns true if validation passes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
| const parseIndexPattern = (pattern: string): PatternSegment[] => { | ||
| if (!pattern) return []; | ||
|
|
||
| const segments: PatternSegment[] = []; | ||
| let currentSegment = ''; | ||
| let wildcardIndex = 0; | ||
|
|
||
| for (let i = 0; i < pattern.length; i++) { | ||
| const char = pattern[i]; | ||
| if (char === '*') { | ||
| if (currentSegment) { | ||
| segments.push({ type: 'static', value: currentSegment }); | ||
| currentSegment = ''; | ||
| } | ||
| segments.push({ type: 'wildcard', value: '*', index: wildcardIndex }); | ||
| wildcardIndex++; | ||
| } else { | ||
| currentSegment += char; | ||
| } | ||
| } | ||
|
|
||
| if (currentSegment) { | ||
| segments.push({ type: 'static', value: currentSegment }); | ||
| } | ||
|
|
||
| return segments; | ||
| }; |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add JSDoc comment to parseIndexPattern explaining that it parses an index pattern into segments of static text and wildcards, with each wildcard receiving a sequential index starting from 0.
| // Run validation and update state, returns true if validation passes | ||
| const runValidation = useCallback( | ||
| async (name: string): Promise<boolean> => { | ||
| setIsValidating(true); | ||
| try { | ||
| const result = await validateStreamName(name, onValidate); | ||
| setValidationError(result.errorType); | ||
| setConflictingIndexPattern(result.conflictingIndexPattern); | ||
| return result.errorType === null; | ||
| } finally { | ||
| setIsValidating(false); | ||
| } | ||
| }, | ||
| [onValidate] | ||
| ); | ||
|
|
||
| // Debounced validation - only runs after first submit attempt with an error | ||
| // When validation passes, reset to "submit only" mode | ||
| useDebounce( | ||
| () => { | ||
| if (hasAttemptedSubmit && validationError !== null) { | ||
| runValidation(streamName).then((isValid) => { | ||
| if (isValid) { | ||
| // Validation passed, reset to "submit only" mode | ||
| setHasAttemptedSubmit(false); | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| VALIDATION_DEBOUNCE_MS, | ||
| [streamName, hasAttemptedSubmit, validationError, runValidation] |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The runValidation function is included in the debounce dependencies array, which will cause the debounce effect to reset whenever onValidate changes. Since runValidation is a stable callback (only depends on onValidate), this is acceptable. However, to make the dependency array more explicit about what actually triggers re-execution, consider extracting the debounced validation logic to avoid including runValidation itself in the dependencies.
| // Run validation and update state, returns true if validation passes | |
| const runValidation = useCallback( | |
| async (name: string): Promise<boolean> => { | |
| setIsValidating(true); | |
| try { | |
| const result = await validateStreamName(name, onValidate); | |
| setValidationError(result.errorType); | |
| setConflictingIndexPattern(result.conflictingIndexPattern); | |
| return result.errorType === null; | |
| } finally { | |
| setIsValidating(false); | |
| } | |
| }, | |
| [onValidate] | |
| ); | |
| // Debounced validation - only runs after first submit attempt with an error | |
| // When validation passes, reset to "submit only" mode | |
| useDebounce( | |
| () => { | |
| if (hasAttemptedSubmit && validationError !== null) { | |
| runValidation(streamName).then((isValid) => { | |
| if (isValid) { | |
| // Validation passed, reset to "submit only" mode | |
| setHasAttemptedSubmit(false); | |
| } | |
| }); | |
| } | |
| }, | |
| VALIDATION_DEBOUNCE_MS, | |
| [streamName, hasAttemptedSubmit, validationError, runValidation] | |
| // Debounced validation - only runs after first submit attempt with an error | |
| // When validation passes, reset to "submit only" mode | |
| useDebounce( | |
| () => { | |
| if (hasAttemptedSubmit && validationError !== null) { | |
| (async () => { | |
| setIsValidating(true); | |
| try { | |
| const result = await validateStreamName(streamName, onValidate); | |
| setValidationError(result.errorType); | |
| setConflictingIndexPattern(result.conflictingIndexPattern); | |
| if (result.errorType === null) { | |
| // Validation passed, reset to "submit only" mode | |
| setHasAttemptedSubmit(false); | |
| } | |
| } finally { | |
| setIsValidating(false); | |
| } | |
| })(); | |
| } | |
| }, | |
| VALIDATION_DEBOUNCE_MS, | |
| [streamName, hasAttemptedSubmit, validationError, onValidate] |
⏳ Build in-progress, with failures
Failed CI StepsTest Failures
|
Summary
Closes #241644
This PR adds the step to name and confirm step to the create classic stream flyout.
Changes
NameAndConfirmStepandNameStreamSectioncomponents.selectedTemplateandonTemplateSelectand moves the state insideCreateClassicStreamFlyout.CreateClassicStreamFlyout.StreamNameInputcomponentImportant
The Confirm template details section will be added in a separate PR to make it easier for review.
How to test
View the component in Storybook:
Checklist
Check the PR satisfies following conditions.
Reviewers should verify this PR satisfies this list as well.
release_note:breakinglabel should be applied in these situations.release_note:*label is applied per the guidelinesbackport:*labels.